@file:Suppress("unused")

package dev.entao.sql

import dev.entao.base.closeSafe
import dev.entao.json.YsonArray
import dev.entao.json.YsonObject
import dev.entao.json.YsonValue
import dev.entao.log.logd
import java.sql.ResultSet
import java.sql.ResultSetMetaData
import kotlin.reflect.full.createInstance


//=============each=================
inline fun ResultSet.each(block: (ResultSet) -> Unit) {
	for (r in this) {
		block(r)
	}
}


inline fun ResultSet.eachJson(block: (YsonObject) -> Unit) {
	val meta = this.metaData
	each {
		block(it.row2Json(meta))
	}
}

inline fun ResultSet.eachMap(block: (ModelMap) -> Unit) {
	val meta = this.metaData
	each {
		block(it.row2Map(meta))
	}
}

//=====all====
inline fun <R> ResultSet.list(block: (ResultSet) -> R?): ArrayList<R> {
	val ls = ArrayList<R>(256)
	for (r in this) {
		ls += block(r) ?: continue
	}
	return ls
}


val ResultSet.listArray: YsonArray
	get() {
		val arr = YsonArray(256)
		val meta = this.metaData
		this.each {
			arr.data += it.row2Json(meta)
		}
		return arr
	}
val ResultSet.listJson: ArrayList<YsonObject>
	get() {
		val meta = this.metaData
		return list {
			it.row2Json(meta)
		}
	}

val ResultSet.listMap: ArrayList<ModelMap>
	get() {
		val meta = this.metaData
		return this.list { it.row2Map(meta) }
	}

fun <T : Model> ResultSet.listModel(block: () -> T): List<T> {
	return this.listMap.map {
		block().apply { model.putAll(it) }
	}
}

inline fun <reified T : Model> ResultSet.listModels(): List<T> {
	return this.listMap.map {
		val m = T::class.createInstance()
		m.model.putAll(it)
		m
	}
}

//=============first=================

val ResultSet.exists: Boolean
	get() {
		this.closeAfter {
			return this.next()
		}
	}

inline fun <R> ResultSet.firstRow(block: (ResultSet) -> R?): R? {
	this.closeAfter {
		if (it.next()) {
			return block(it)
		}
	}
	return null
}

val ResultSet.firstMap: ModelMap? get() = this.firstRow { it.row2Map(it.metaData) }
val ResultSet.firstJson: YsonObject? get() = this.firstRow { it.row2Json(it.metaData) }

inline fun <reified T : Model> ResultSet.firstModel(): T {
	val m = T::class.createInstance()
	m.model.putAll(this.firstMap!!)
	return m
}

fun ResultSet.firstRowString(col: Int = 1): String? {
	return firstRow { it.getString(col) }
}

fun ResultSet.firstRowString(col: String): String? {
	return firstRow { it.getString(col) }
}

fun ResultSet.firstRowInt(col: Int = 1): Int? {
	return firstRow { it.getInt(col) }
}

fun ResultSet.firstRowInt(col: String): Int? {
	return firstRow { it.getInt(col) }
}

fun ResultSet.firstRowLong(col: Int = 1): Long? {
	return firstRow { it.getLong(col) }
}

fun ResultSet.firstRowLong(col: String): Long? {
	return firstRow { it.getLong(col) }
}

fun ResultSet.firstRowDouble(col: Int = 1): Double? {
	return firstRow { it.getDouble(col) }
}

fun ResultSet.firstRowDouble(col: String): Double? {
	return firstRow { it.getDouble(col) }
}
//==============utils==============

inline fun <R> ResultSet.closeAfter(block: (ResultSet) -> R): R {
	var closed = false
	try {
		return block(this)
	} catch (e: Exception) {
		closed = true
		try {
			this.closeWithStatement()
		} catch (closeException: Exception) {
		}
		throw e
	} finally {
		if (!closed) {
			this.closeWithStatement()
		}
	}
}

fun ResultSet.getJson(col: Int): YsonValue? {
	val js = this.getString(col) ?: return null
	return if (js.startsWith("{")) {
		YsonObject(js)
	} else if (js.startsWith("[")) {
		YsonArray(js)
	} else {
		null
	}
}

fun ResultSet.getJsonObject(col: Int): YsonObject? {
	val js = this.getString(col) ?: return null
	return if (js.startsWith("{")) {
		YsonObject(js)
	} else {
		null
	}
}

fun ResultSet.getJsonArray(col: Int): YsonArray? {
	val js = this.getString(col) ?: return null
	return if (js.startsWith("[")) {
		YsonArray(js)
	} else {
		null
	}
}


fun ResultSet.row2Map(meta: ResultSetMetaData): ModelMap {
	val map = ModelMap()
	for (i in 1..meta.columnCount) {
		map[meta.getColumnLabel(i)] = this.getObject(i)
	}
	return map
}

fun ResultSet.row2Json(meta: ResultSetMetaData): YsonObject {
	val yo = YsonObject(meta.columnCount + 2)
	for (i in 1..meta.columnCount) {
		val label = meta.getColumnLabel(i)
		val typeName = meta.getColumnTypeName(i)
		val value: Any? = if (typeName in jsonTypes) {
			val js = this.getString(i)?.trim()
			if (js == null || js.isEmpty()) {
				null
			} else if (js.startsWith("{")) {
				YsonObject(js)
			} else if (js.startsWith("[")) {
				YsonArray(js)
			} else {
				null
			}
		} else {
			this.getObject(i)
		}
		yo.putAny(label, value)
	}
	return yo
}

fun ResultSet.closeWithStatement() {
	val st = this.statement
	this.closeSafe()
	st?.closeSafe()
}

private val jsonTypes: Set<String> = setOf("json", "JSON", "jsonb", "JSONB")


fun ResultSet.dump() {
	val meta = this.metaData
	val sb = StringBuilder(512)
	this.each {
		sb.setLength(0)
		for (i in 1..meta.columnCount) {
			val label = meta.getColumnLabel(i)
			val value = this.getObject(i)
			sb.append(label).append("=").append(value).append(", ")
		}
		logd(sb.toString())
	}
}


class ResultSetIterator(val rs: ResultSet) : Iterator<ResultSet> {
	override operator fun next(): ResultSet {
		return rs
	}

	override operator fun hasNext(): Boolean {
		val b = rs.next()
		if (!b) {
			rs.closeWithStatement()
		}
		return b
	}
}

operator fun ResultSet.iterator(): Iterator<ResultSet> {
	return ResultSetIterator(this)
}


inline operator fun <reified T> ResultSet.get(key: String): T {
	return when (T::class) {
		String::class -> this.getString(key)
		Long::class -> this.getLong(key)
		Int::class -> this.getInt(key)
		Short::class -> this.getShort(key)
		Byte::class -> this.getByte(key)
		Float::class -> this.getFloat(key)
		Double::class -> this.getDouble(key)
		Boolean::class -> this.getBoolean(key)
		ByteArray::class -> this.getBlob(key)
		java.sql.Date::class -> this.getDate(key)
		java.sql.Time::class -> this.getTime(key)
		java.sql.Timestamp::class -> this.getTimestamp(key)
		Any::class -> this.getObject(key)
		else -> error("Unsupport type ${T::class}")
	} as T
}

inline operator fun <reified T> ResultSet.get(idx: Int): T {
	return when (T::class) {
		String::class -> this.getString(idx)
		Long::class -> this.getLong(idx)
		Int::class -> this.getInt(idx)
		Short::class -> this.getShort(idx)
		Byte::class -> this.getByte(idx)
		Float::class -> this.getFloat(idx)
		Double::class -> this.getDouble(idx)
		Boolean::class -> this.getBoolean(idx)
		ByteArray::class -> this.getBlob(idx)
		java.sql.Date::class -> this.getDate(idx)
		java.sql.Time::class -> this.getTime(idx)
		java.sql.Timestamp::class -> this.getTimestamp(idx)
		Any::class -> this.getObject(idx)
		else -> error("Unsupport type ${T::class}")
	} as T
}
